:- import "%%/lib/dynamic.sl" #
:- import "%%/lib/list.sl" #
:- import "%%/lib/os.sl" #


-- Process Invocation --

make .t :-
	concat .t .target, -- Convert to string type
	findAllDepends .target,
	makeTarget .target $ #

makeTarget .target . :- search (MAKE.DONE .target) ! #
makeTarget .target .history :-
	search (DEPEND .target .deps)
	,
	-- Make sure all dependencies are ready
	forall (list.member .deps .dep) (
		list.member .history .dep; -- No infinite recursion
		makeTarget .dep (.dep, .history)
	)
	,
	-- Only start processing if there are dependencies violated
	(
		getLastMod .target .time,
		forall (list.member .deps .dep) (
			getLastMod .dep .depTime,
			.time > .depTime
		);
		processTarget .target -- Really have to make it
	)
	!
	-- Update flags
	delete.all (LAST.MOD .target .),
	insert (MAKE.DONE .target) #

getLastMod .file .time :- search (LAST.MOD .file .time) ! #
getLastMod .file .time :-
	file.getLastMod .file .time,
	insert (LAST.MOD .file .time) #


-- Dependencies Generator --

findAllDepends .target :- findAllDepends .target . #

findAllDepends .goal .deps :- -- Skip if already defined
	search (DEPEND .goal .deps) ! #
findAllDepends .goal .deps :-
	findDepends .goal .deps !
--	write "[DEBUG]" .goal "depends on:" .deps, nl,
	insert (DEPEND .goal .deps),
	forall (list.member .deps .dep) (findAllDepends .dep .) #


-- Dependency Rules --

findDepends .target .deps :- .deps => .target # -- Manual input

findDepends .target .deps :- -- Object file from its C++ source
	file.nameExt .target .name "o", concat .name ".cpp" .cpp,
	file.exists .cpp !
	findSourceDepends .cpp .deps #

findDepends . $ # -- Default to no dependencies

	-- Find depends from sources by looking for include directives
findSourceDepends .source .deps :- findSourceDepends .source $ .deps #

findSourceDepends .source . .deps :-
	search (SOURCE.DEPEND .source .deps) ! #
findSourceDepends .source . $ :- -- File we cannot find, no dependencies
	not (file.exists .source) ! #
findSourceDepends .source .history .deps :-
	scanIncludes .source .includes, -- Looks into each source file
	forall (list.member .includes .include) (
		list.member .history .include; -- Avoids infinite recursion
		findSourceDepends .include (.source, .history) .
	),
	mergeSourceDepends .includes .allIncludes, -- Merge the source dependencies
	.deps = (.source, .allIncludes), -- Includes the source itself
	insert (SOURCE.DEPEND .source .deps) # -- Saves as record

mergeSourceDepends $ $ #
mergeSourceDepends (.first, .remains) .allDeps :-
	search (SOURCE.DEPEND .first .firstDeps); .firstDeps = $ !
	mergeSourceDepends .remains .remainDeps,
	list.merge .firstDeps .remainDeps .allDeps #

	-- C++/header sources
scanIncludes .source .list :-
	file.nameExt .source . .ext, (.ext = "h"; .ext = "cpp") !
	ascii .quote 34,
	concat "awk '/#include[ ]+\" .quote ".*\" .quote
		"/ { print $2 }' " .source .command,
	command.lines .command .quoted,
	unquoteList .quoted .list #
scanIncludes . $ #


-- Compiling/Linking Rules --

run .exec :- .exec # -- Without predicate undefined warnings

processTarget .target :- -- Manual input
	rule (process .target); rule (process .target :- .) !
	run (process .target);
	processFailed .target #

processTarget .o :- -- Generates object file by compiling C++ source
	file.nameExt .o .name "o" !
	flags.cpp .flags,
	concat "g++ " .flags " -c -o " .o " " .name ".cpp" .command,
	execute .command;
	processFailed .o #

processTarget .target :- -- Nothing to do to generate sources
	file.nameExt .target . .ext, (.ext = "h"; .ext = "cpp") ! #

processTarget .target :- -- Defined as alias, no processing actions
	search (DEPEND .target .deps),
	not (.deps = $) # -- To err on null targets (no deps/no process)

processTarget .target :- processFailed .target #

processFailed .target :-
	write "Processing failed for target" .target, nl, fail #


-- Routines for Build Processes --

link .target :- -- Link all depended files by default
	findDepends .target .objs, link .objs .target #
link .objects .target :-
	flags.link .flags,
	execute ("g++ " .objects .flags " -o " .target) #

clean .files :- execute ("rm -f " .files) #


-- Helpers --

	-- This does not check the result is really quoted
unquoteList $ $ #
unquoteList (.first, .remains) (.first1, .remains1) :-
	substr .first 1 -1 .first1,
	unquoteList .remains .remains1 #
